#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "download.h"

/// @brief 通过tftp下载文件到本地
/// @param sockfd 套接字
/// @param server_addr 存放服务器IP地址和端口的结构体
/// @param isset 是否设置选项标志位
void download(int sockfd, struct sockaddr_in *server_addr, char *isset)
{
    // 获取用户输入的每次发送的块大小
    char blcsize_stdin[16] = "";
    // 获取重发时间timeout
    char timeout_stdin[2];
    // 接收客户端想下载的文件名
    char filename[128] = "";
    while (1)
    {
        if (strcmp(isset, "set") == 0)
        {
            while (1)
            {
                printf("blocksize(<=65535):");
                scanf("%s", blcsize_stdin);
                getchar();
                if (atoi(blcsize_stdin) > 65535 || atoi(blcsize_stdin) <= 0)
                {
                    printf("输入有误，重新输入\n");
                    continue;
                }
                else
                {
                    break;
                }
            }
            while (1)
            {
                printf("timeout(<=60):");
                scanf("%s", timeout_stdin);
                getchar();
                if (atoi(timeout_stdin) > 60 || atoi(timeout_stdin) <= 0)
                {
                    printf("输入有误，重新输入\n");
                    continue;
                }
                else
                {
                    break;
                }
            }
            printf("请输入要下载的文件：");
            scanf("%s", filename);
            getchar();
            break;
        }
        else if (strcmp(isset, "file") == 0)
        {
            printf("请输入要下载的文件：");
            scanf("%s", filename);
            getchar();
            break;
        }
        else
        {
            printf("输入有误，请重新输入\n");
        }
    }
    // 请求报文内容
    unsigned char text[1024] = "";
    // 结构体长度
    socklen_t server_addr_len = sizeof(struct sockaddr);
    // 根据有没有选项执行不同的分支
    if (strcmp(isset, "set") == 0)
    {
        // 接收带选项报文大小
        int yes_set_text_len = sprintf(text,
                                       "%c%c%s%c%s%c%s%c%s%c%s%c%s%c%s%c%c%c",
                                       0, 1, filename, 0,
                                       "netascii", 0,
                                       "blksize", 0, blcsize_stdin, 0,
                                       "timeout", 0, timeout_stdin, 0,
                                       "tsize", 0, '0', 0);
        if (sendto(sockfd, text, yes_set_text_len, 0, (struct sockaddr *)server_addr, server_addr_len) < 0)
        {
            perror("请求报文发送失败");
            exit(EXIT_FAILURE);
        }
        // 接收OACK
        unsigned char recv_OACK_buf[1024] = "";
        // 接收包的长度
        ssize_t recv_OACK_len = recvfrom(sockfd, recv_OACK_buf, sizeof(recv_OACK_buf), 0,
                                         (struct sockaddr *)server_addr, &server_addr_len);
        // 确保是否接收到
        if (recv_OACK_len < 0)
        {
            perror("recvfrom接收失败");
            exit(EXIT_FAILURE);
        }

        // 获取操作码
        uint16_t op_code;
        memcpy(&op_code, recv_OACK_buf, 2);
        op_code = ntohs(op_code);
        // 判断操作码是不是6（OACK）
        if (op_code != 6)
        {
            perror("非OACK包");
            exit(EXIT_FAILURE);
        }

        char *tmp = recv_OACK_buf + 2;
        int offset = 2; // 记录当前解析到的位置

        int block_size = 512; // 默认值
        int timeout_size = 5; // 默认值
        int tsize = 0;        // 默认值

        // 获取选项大小并赋值
        while (offset < recv_OACK_len)
        {
            // 获取选项名
            char *option_name = tmp;
            // 获取选项名长度
            int name_len = strlen(option_name);
            // 判断选项名是否读取成功
            if (name_len == 0)
            {
                perror("无效选项名");
                exit(EXIT_FAILURE);
            }
            // 更新offset
            offset += name_len + 1;
            // 更新指针
            tmp += name_len + 1;

            // 获取选项数据
            char *value = tmp;
            int value_len = strlen(value);

            // 更新offset
            offset += value_len + 1;
            // 更新指针
            tmp += value_len + 1;

            // 给选项赋值
            if (strcmp("blksize", option_name) == 0)
            {
                block_size = atoi(value);
            }
            else if (strcmp("timeout", option_name) == 0)
            {
                timeout_size = atoi(value);
            }
            else if (strcmp("tsize", option_name) == 0)
            {
                tsize = atoi(value);
            }
        }

        // 发送回复OACK的ACK包 00 04 00 00
        char ACK[4] = "";
        ACK[1] = 4;
        // 发送ACK并接收发送长度
        ssize_t send_len = sendto(sockfd, ACK, 4, 0, (struct sockaddr *)server_addr, server_addr_len);
        // 判断发送是否成功
        if (send_len != 4)
        {
            perror("ACK发送失败");
            exit(EXIT_FAILURE);
        }
        // 块数据核对变量
        uint16_t block_count = 1;
        // 打开本地文件
        int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        while (1)
        {
            char recv_DATA_buf[65535] = "";
            ssize_t recv_DATA_len =
                recvfrom(sockfd, recv_DATA_buf, 4 + block_size,
                         0, (struct sockaddr *)server_addr, &server_addr_len);
            if (recv_DATA_len < 0)
            {
                perror("接收数据失败");
                exit(EXIT_FAILURE);
            }
            // 获取操作码
            uint16_t op_code_DATA;
            memcpy(&op_code_DATA, recv_DATA_buf, 2);
            op_code_DATA = ntohs(op_code_DATA);
            // 判断操作码是不是3（DATA）
            if (op_code_DATA != 3)
            {
                perror("非DATA包");
                exit(EXIT_FAILURE);
            }
            // 获取块数据
            uint16_t block_data_DATA;
            memcpy(&block_data_DATA, recv_DATA_buf + 2, 2);
            block_data_DATA = ntohs(block_data_DATA);
            // 获取内容
            char recv_data_DATA[65535] = "";
            memcpy(recv_data_DATA, recv_DATA_buf + 4, block_size);

            // 写入数据
            if (block_count == block_data_DATA)
            {
                ssize_t write_len = write(fd, recv_data_DATA, strlen(recv_data_DATA));
                if (write_len < 0)
                {
                    perror("写入失败");
                    exit(EXIT_FAILURE);
                }
            }
            else
            {
                perror("块数据错误");
                exit(EXIT_FAILURE);
            }
            // 接收成功返回一个ACK
            sprintf(ACK, "%c%c%u", 0, 4, block_count);
            while (sendto(sockfd, ACK, 4, 0, (struct sockaddr *)server_addr, server_addr_len) <= 0)
            {
                perror("ACK发送失败，重新发送");
            }
            block_count++;
            if (recv_DATA_len < block_size + 4)
            {
                printf("下载完成\n");
                break;
            }
            close(fd);
        }
    }
    else if (strcmp(isset, "file") == 0)
    {
        // 接收不带选项的报文大小
        int no_set_text_len = sprintf(text, "%c%c%s%c%s%c",
                                      0, 1, filename, 0,
                                      "netascii", 0);
        if (sendto(sockfd, text, no_set_text_len, 0, (struct sockaddr *)server_addr, server_addr_len) < 0)
        {
            perror("请求报文发送失败");
            exit(EXIT_FAILURE);
        }
        // 块数据核对变量
        uint16_t block_count = 1;
        // 打开本地文件
        int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        while (1)
        {
            char recv_DATA_buf[516] = "";
            ssize_t recv_DATA_len =
                recvfrom(sockfd, recv_DATA_buf, sizeof(recv_DATA_buf),
                         0, (struct sockaddr *)server_addr, &server_addr_len);
            if (recv_DATA_len < 0)
            {
                perror("接收数据失败");
                exit(EXIT_FAILURE);
            }
            // 获取操作码
            uint16_t op_code_DATA;
            memcpy(&op_code_DATA, recv_DATA_buf, 2);
            op_code_DATA = ntohs(op_code_DATA);
            // 判断操作码是不是3（DATA）
            if (op_code_DATA != 3)
            {
                perror("非DATA包");
                exit(EXIT_FAILURE);
            }
            // 获取块数据
            uint16_t block_data_DATA;
            memcpy(&block_data_DATA, recv_DATA_buf + 2, 2);
            block_data_DATA = ntohs(block_data_DATA);
            // 获取内容
            char recv_data_DATA[512] = "";
            memcpy(recv_data_DATA, recv_DATA_buf + 4, strlen(recv_DATA_buf + 4));

            // 写入数据
            if (block_count == block_data_DATA)
            {
                ssize_t write_len = write(fd, recv_data_DATA, strlen(recv_data_DATA));
                if (write_len < 0)
                {
                    perror("写入失败");
                    exit(EXIT_FAILURE);
                }
            }
            else
            {
                perror("块数据错误");
                exit(EXIT_FAILURE);
            }
            char ACK[4] = "";
            // 接收成功返回一个ACK
            sprintf(ACK, "%c%c%u", 0, 4, block_count);
            while (sendto(sockfd, ACK, 4, 0, (struct sockaddr *)server_addr, server_addr_len) <= 0)
            {
                perror("ACK发送失败，重新发送");
            }
            block_count++;
            if (recv_DATA_len < sizeof(recv_DATA_buf))
            {
                printf("下载完成\n");
                break;
            }
            close(fd);
        }
    }
}